﻿//CommissionManager.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo.Commissions
{
	using System;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Менеджер расчета комиссии.
	/// </summary>
	public class CommissionManager : ICommissionManager
	{
		private sealed class CommissionRuleList : SynchronizedSet<CommissionRule>
		{
			private CommissionRule _perOrderRule;
			private CommissionRule _perOrderVolumeRule;
			private Tuple<int, CommissionRule> _orderCountRuleInfo;
			private int _currentOrderCount;

			private CommissionRule _perTradeRule;
			private CommissionRule _perTradePriceRule;
			private CommissionRule _perTradeVolumeRule;
			private Tuple<int, CommissionRule> _tradeCountRuleInfo;
			private int _currentTradeCount;

			private Tuple<Security, CommissionRule> _securityRule;
			private Tuple<SecurityTypes, CommissionRule> _securityTypeRule;
			private Tuple<ExchangeBoard, CommissionRule> _exchangeBoardRule;

			public CommissionRuleList()
			{
				foreach (var type in Enumerator.GetValues<CommissionTypes>())
					_commissionPerTypes.Add(type, 0);
			}

			private readonly SynchronizedDictionary<CommissionTypes, decimal> _commissionPerTypes = new SynchronizedDictionary<CommissionTypes, decimal>();

			public SynchronizedDictionary<CommissionTypes, decimal> CommissionPerTypes
			{
				get { return _commissionPerTypes; }
			}

			private decimal Process<T>(Tuple<T, CommissionRule> tuple)
			{
				return Process(tuple.Item2, (decimal)tuple.Item2.Value);
			}

			private decimal Process(CommissionRule rule, Unit commission)
			{
				return Process(rule, (decimal)commission);
			}

			private decimal Process(CommissionRule rule, decimal commission)
			{
				_commissionPerTypes[rule.Type] += commission;
				return commission;
			}

			public decimal GetCommission(MyTrade trade)
			{
				var commission = 0m;

				if (_perTradePriceRule != null)
					commission += Process(_perTradePriceRule, trade.Trade.Price * trade.Trade.Volume * _perTradePriceRule.Value);

				if (_perTradeVolumeRule != null)
					commission += Process(_perTradeVolumeRule, trade.Trade.Volume * _perTradeVolumeRule.Value);

				if (_tradeCountRuleInfo != null)
				{
					if (++_currentTradeCount > _tradeCountRuleInfo.Item1)
					{
						_currentTradeCount = 0;
						commission += Process(_tradeCountRuleInfo);
					}
				}

				if (_securityRule != null)
				{
					if (trade.Trade.Security == _securityRule.Item1)
						commission += Process(_securityRule);
				}

				if (_securityTypeRule != null)
				{
					if (trade.Trade.Security.Type == _securityTypeRule.Item1)
						commission += Process(_securityTypeRule);
				}

				if (_exchangeBoardRule != null)
				{
					if (trade.Trade.Security.ExchangeBoard == _exchangeBoardRule.Item1)
						commission += Process(_exchangeBoardRule);
				}

				if (_perTradeRule != null)
					commission += Process(_perTradeRule, _perTradeRule.Value);

				return commission;
			}

			public decimal GetCommission(Order order)
			{
				var commission = 0m;

				if (_orderCountRuleInfo != null)
				{
					if (++_currentOrderCount > _orderCountRuleInfo.Item1)
					{
						_currentOrderCount = 0;
						commission += Process(_orderCountRuleInfo);
					}
				}

				if (_perOrderVolumeRule != null)
					commission += Process(_perOrderVolumeRule, order.Volume * _perOrderVolumeRule.Value);

				if (_perOrderRule != null)
					commission += Process(_perOrderRule, _perOrderRule.Value);

				return commission;
			}

			protected override void OnAdded(CommissionRule item)
			{
 				 base.OnAdded(item);

				switch (item.Type)
				{
					case CommissionTypes.PerOrder:
						_perOrderRule = item;
						break;
					case CommissionTypes.PerOrderVolume:
						_perOrderVolumeRule = item;
						break;
					case CommissionTypes.OrderCount:
						_orderCountRuleInfo = new Tuple<int, CommissionRule>((int)item.TypeValue, item);
						break;
					case CommissionTypes.PerTrade:
						_perTradeRule = item;
						break;
					case CommissionTypes.PerTradePrice:
						_perTradePriceRule = item;
						break;
					case CommissionTypes.PerTradeVolume:
						_perTradeVolumeRule = item;
						break;
					case CommissionTypes.TradeCount:
						_tradeCountRuleInfo = new Tuple<int, CommissionRule>((int)item.TypeValue, item);
						break;
					case CommissionTypes.Security:
						break;
					case CommissionTypes.SecurityType:
						_securityTypeRule = new Tuple<SecurityTypes, CommissionRule>((SecurityTypes)item.TypeValue, item);
						break;
					case CommissionTypes.TurnOver:
						break;
					default:
						throw new ArgumentOutOfRangeException();
				}
			}

			protected override void OnCleared()
			{
				base.OnCleared();

				_perOrderRule = null;
				_perOrderVolumeRule = null;
				_orderCountRuleInfo = null;
				_currentOrderCount = 0;

				_perTradeRule = null;
				_perTradePriceRule = null;
				_perTradeVolumeRule = null;
				_tradeCountRuleInfo = null;
				_currentTradeCount = 0;

				_securityRule = null;
				_securityTypeRule = null;
				_exchangeBoardRule = null;
			}
		}

		private readonly object _syncRoot = new object();

		/// <summary>
		/// Создать <see cref="CommissionManager"/>.
		/// </summary>
		public CommissionManager()
		{
		}

		/// <summary>
		/// Суммарное значение комиссии, сгруппированное по типам.
		/// </summary>
		public SynchronizedDictionary<CommissionTypes, decimal> CommissionPerTypes
		{
			get { return _rules.CommissionPerTypes; }
		}

		private readonly CommissionRuleList _rules = new CommissionRuleList();

		/// <summary>
		/// Тарифный план.
		/// </summary>
		public IList<CommissionRule> Rules
		{
			get { return _rules; }
		}

		/// <summary>
		/// Суммарное значение комиссии.
		/// </summary>
		public virtual decimal Commission { get; private set; }

		/// <summary>
		/// Обнулить <see cref="ICommissionManager.Commission"/>.
		/// </summary>
		public virtual void Reset()
		{
			lock (_syncRoot)
				Commission = 0;
		}

		/// <summary>
		/// Рассчитать комиссию сделки.
		/// </summary>
		/// <param name="trade">Сделка.</param>
		/// <returns>Комиссия сделки.</returns>
		public virtual decimal ProcessMyTrade(MyTrade trade)
		{
			var commission = _rules.GetCommission(trade);

			lock (_syncRoot)
				Commission += commission;

			return commission;
		}

		/// <summary>
		/// Рассчитать комиссию заявки.
		/// </summary>
		/// <param name="order">Заявка.</param>
		/// <returns>Комиссия заявки.</returns>
		public virtual decimal ProcessOrder(Order order)
		{
			var commission = _rules.GetCommission(order);

			lock (_syncRoot)
				Commission += commission;

			return commission;
		}
	}
}